import { RaycastAABB, RaycastBox } from "./Math";
import { ObjNode } from "./ObjNode";

export const GetMaxLevel = function (obj: ObjNode, oct: Octree, ratio: number = 1.0) {

    let maxLevel = oct.object_level >> 8;

    if (ratio <= 0) {
        obj.maxLevel = maxLevel;
        return maxLevel;
    }

    let a = obj.bound;
    let b = oct.bound;
    let avgB = (b[3] + b[4] + b[5]) * 2.0; //halfSize * 2.0
    let avgA = (a[3] - a[0]) + (a[4] - a[1]) + (a[5] - a[2]);
    maxLevel = ~~(Math.log2(avgB / (avgA * ratio)));

    obj.maxLevel = maxLevel;

    return maxLevel;

}



export class Octree {

    id: number = 0;
    idxs: number = 0;
    level: number = 0;
    parent: Octree = null;
    bound: Array<number> = [];
    nodes: Array<Octree> = [];
    objects: Array<ObjNode> = [];
    object_level: number = (8 << 8) | 4;

    // static count:number = 0;

    constructor(bound: Array<number> = [], object_level: number = ((8 << 8) | 4), level: number = 0, parent: Octree | null = null, id: number = 0) {
        this.id = id;
        this.idxs = 0;
        this.nodes = [];
        this.objects = [];
        this.bound = bound;
        this.level = level;
        this.parent = parent;
        this.object_level = object_level;
    }


    split() {

        let level = this.level;
        let object_level = this.object_level - 1;
        let nodes = this.nodes, bound = this.bound;
        let x = bound[0], y = bound[1], z = bound[2],
            w = bound[3] * 0.5, h = bound[4] * 0.5, d = bound[5] * 0.5;

        nodes[0] = new Octree([x + w, y - h, z - d, w, h, d], object_level, level + 1, this, 0);
        nodes[1] = new Octree([x - w, y - h, z - d, w, h, d], object_level, level + 1, this, 1);
        nodes[2] = new Octree([x - w, y + h, z - d, w, h, d], object_level, level + 1, this, 2);
        nodes[3] = new Octree([x + w, y + h, z - d, w, h, d], object_level, level + 1, this, 3);
        nodes[4] = new Octree([x + w, y - h, z + d, w, h, d], object_level, level + 1, this, 4);
        nodes[5] = new Octree([x - w, y - h, z + d, w, h, d], object_level, level + 1, this, 5);
        nodes[6] = new Octree([x - w, y + h, z + d, w, h, d], object_level, level + 1, this, 6);
        nodes[7] = new Octree([x + w, y + h, z + d, w, h, d], object_level, level + 1, this, 7);


    }

    getIndex(pRect: Array<number>) {

        let idxs = 0, bound = this.bound;
        let x = bound[0], y = bound[1], z = bound[2];

        let L = pRect[0] < x, R = pRect[3] >= x;
        let D = pRect[1] < y, U = pRect[4] >= y;
        let B = pRect[2] < z, F = pRect[5] >= z;

        if (R && D) { B && (idxs = 1); F && (idxs |= (1 << 16)); }
        if (L && D) { B && (idxs |= (1 << 4)); F && (idxs |= (1 << 20)); }
        if (L && U) { B && (idxs |= (1 << 8)); F && (idxs |= (1 << 24)); }
        if (R && U) { B && (idxs |= (1 << 12)); F && (idxs |= (1 << 28)); }

        return idxs;
    }

    remove(obj: ObjNode) {

        let octrees = obj.targets;
        for (let i = octrees.length - 1; i >= 0; i--) {
            let node = octrees[i];
            let objects = node.objects;
            let idx = objects.indexOf(obj);
            if (idx != -1) {

                //remove obj
                let end = objects.length - 1;
                objects[idx] = objects[end];
                objects.length = end;

                //combine tree
                while (end == 0 && node.idxs == 0) {

                    node.reset();
                    let id = node.id;
                    node = node.parent;
                    if (!node) break; //head = null, break;

                    end = node.objects.length;
                    node.idxs &= (~(0xF << (id* 4)));
                }
            }
        }
        octrees.length = 0;
    }

    insert(obj: ObjNode) {

        let level = this.level,
            nodes = this.nodes,
            objects = this.objects,
            object_level = this.object_level;

        if (nodes.length) {
            if (obj.maxLevel != level) {
                let j = 0, idxs = this.getIndex(obj.bound);
                this.idxs |= idxs;
                while (idxs) {
                    if (idxs & 0xF) nodes[j].insert(obj);
                    idxs = idxs >> 4;
                    j++;
                }

            } else {
                objects.push(obj);
                obj.insert(this);
            }

            return;
        }

        objects.push(obj);
        obj.insert(this);

        if (objects.length > (object_level >> 8) && (object_level & 0xFF)) {
            this.split();
            let inc = 0, length = objects.length;
            for (let i = 0; i < length; i++) {
                let obj = objects[i];
                if (obj.maxLevel != level) {
                    obj.remove(this);
                    let j = 0, idxs = this.getIndex(obj.bound);
                    this.idxs |= idxs;
                    while (idxs) {
                        if (idxs & 0xF) nodes[j].insert(obj);
                        idxs = idxs >> 4;
                        j++;
                    }
                } else {
                    objects[inc++] = objects[i];
                }
            }

            objects.length = inc;
        }
    }


    retrieve(bound: Array<number>, flag: number, result: Array<ObjNode>, mask: number = 0xFFFFFFFF) {

        let inc = 0;
        let A = bound;
        let queue:Array<Octree> = [this];

        while (inc >= 0) {

            let octree = queue[inc--];

            let idxs = octree.idxs;
            let nodes = octree.nodes;
            let objects = octree.objects;

            for (let i = 0,j = objects.length; i < j; i++) {
                let obj = objects[i], B = obj.bound;
                if (obj.flag < flag) {
                    obj.flag = flag;
                    if (mask & obj.group) {
                        if (!(B[0] > A[3] || A[0] > B[3]
                            || B[1] > A[4] || A[1] > B[4]
                            || B[2] > A[5] || A[2] > B[5])) {
                            result.push(obj);
                        }
                    }
                }
            }

            if( idxs ){
                idxs&=octree.getIndex(A);
                for (let i = 0; idxs != 0; idxs>>=4, i++) {
                    if (idxs & 0xF) {
                        queue[++inc] = nodes[i];
                    }
                }
            }
        }

    }

    rayCast(ray: Array<number>, flag: number, result: Array<ObjNode>, mask: number = 0xFFFFFFFF) {

        let max = ray[6];
        let o: Array<number> = [ray[0], ray[1], ray[2]]; //origin
        let n: Array<number> = [ray[3], ray[4], ray[5]]; //normal

        let inc = 0;
        let queue: Array<Octree> = [this];

        while (inc >= 0) {

           // Octree.count++;
            let octree = queue[inc--];

            if(RaycastBox(o, n, octree.bound, max) != null){
                let idxs = octree.idxs;
                let nodes = octree.nodes;
                let objects = octree.objects;
      
                for (let i = 0, j = objects.length; i < j; i++) {
                    let obj = objects[i], b = obj.bound;
    
                    if (obj.flag < flag) {
                        obj.flag = flag;
                        if (mask & obj.group) {
                            let dist = RaycastAABB(o, n, b, max);
                            if (dist != null) {
                                obj.rayDist = dist;
                                result.push(obj)
                            }
                        }
                    }
                }

                for (let i = 0; idxs != 0; idxs>>=4, i++) {
                    if (idxs & 0xF) {
                        queue[++inc] = nodes[i];
                    }
                }

            }
        }
    }


    reset() {

        let nodes = this.nodes;
        for (let i = nodes.length - 1; i >= 0; i--) {
            let node = nodes[i];
            if (node) {
                node.clear();
                nodes[i] = null;
            }
        }

        this.idxs = 0;
        this.nodes = [];
        this.objects = [];
    }

    clear() {

        let nodes = this.nodes;
        for (let i = nodes.length - 1; i >= 0; i--) {
            let node = nodes[i];
            if (node) {
                node.clear();
                nodes[i] = null;
            }
        }

        this.nodes = null;
        this.bound = null;
        this.objects = null;
    }


}
